package ils.di;

import ils.domain.catalog.model.BookRepository;
import ils.domain.catalog.model.CategoryRepository;
import ils.domain.issue.model.IssueRepository;
import ils.domain.issue.model.ItemRepository;
import ils.domain.issue.model.ReserveRepository;
import ils.domain.issue.model.ShelfRepository;
import ils.domain.model.UserRepository;
import ils.domain.ugc.model.ReviewRepository;
import ils.services.*;
import ils.util.HibernateUtil;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: Rejeev Divakaran
 * Date: 7/24/13
 */
public class ObjectBuilderImpl implements ObjectBuilder {
    private static final Logger LOGGER = LoggerFactory.getLogger(ObjectBuilderImpl.class);
    private Map<Class, Object> objectMap = new ConcurrentHashMap<>();

    @Override
    public <T> T getInstance(Class<T> type, boolean shared){
        if(!shared) return construct(type, shared);
        else {
            T object = (T)objectMap.get(type);
            if(object == null){
                object = construct(type, shared);
                objectMap.put(type, object);
            }
            return object;
        }
    }

    protected <T> T construct(Class<T> type, boolean shared){
        try {
            if(type.equals(SessionFactory.class)) return (T) HibernateUtil.getSessionFactory();
            String implimentationClassName = type.getName() + "Impl";
            Class<T> aClass = null;
            try {
                aClass = (Class<T>)Class.forName(implimentationClassName);
                //aClass = (Class<T>)this.getClass().getClassLoader().loadClass(implimentationClassName);
            } catch (ClassNotFoundException e) {
                LOGGER.debug("Could not load the class", e);
                aClass = type;
            }
            T object = aClass.newInstance();
            for(Class managedClass : managedClasses){
                try {
                    Method method  = aClass.getMethod("set" + managedClass.getSimpleName(), managedClass);
                    method.invoke(object, getInstance(managedClass, shared));
                } catch (Exception e) {LOGGER.debug("No matching method: set" + managedClass.getName() + " in the class " + aClass.getName());}
            }
            return object;
        } catch (Exception e) {
            LOGGER.error("Exception while constructing the object", e);
            throw new RuntimeException("Exception while constructing the object", e);
        }
    }

    private static final Class[] managedClasses = {
            AdminService.class,
            AquisitionCommon.class,
            AquisitionService.class,
            AuthenticationService.class,
            BookRepository.class,
            CategoryRepository.class,
            SessionFactory.class,
            IssueService.class,
            ReserveRepository.class,
            ItemRepository.class,
            IssueRepository.class,
            ShelfRepository.class,
            UserRepository.class,
            ReviewRepository.class
    };
}
